Skip to content

[12.x] Add config_or_fail helper #56255

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: 12.x
Choose a base branch
from

Conversation

dallyger
Copy link
Contributor

@dallyger dallyger commented Jul 10, 2025

Add getOrFail() to Config repository (with tests)
Add config_or_fail() helper in Foundation

The implementation should behave similarly to the
\Illuminate\Support\Env::getOrFail() method.

My use-case for this would be to quickly check, that the user did not forget to set some environment variables while deployment. Some configs may not have a good default value but are also not that important to block an entire deployment if just that small feature fails.

Example config:

# services.php

return [
  'xyz' => [
    'active' => (bool) env('XYZ_ACTIVE', false),
    'domain' => env('XYZ_DOMAIN'),
  ],
];

With that, you may use it like so:

if (config('services.xyz.active')) {
  Http::post(config_or_fail('services.xyz.domain').'/up');
}

dallyger added 2 commits July 10, 2025 11:28
The implementation should behave similarly to the
`\Illuminate\Support\Env::getOrFail()` method.
*/
public function getOrFail(string $key)
{
return $this->get($key) ?? throw new RuntimeException("Configuration value for key [$key] is not set.");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if the config is actually null this will not be ok.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is true, but also expected. If you know it may be null, you are probably better off using the regular get() method. As I said, this is similar to the env() helper.

What is your expectation on how the method should work? Only throw if the config key is missing? That would hardly be of any use, as most config keys should be static as they're build from the files in ./config/ returning arrays.

@macropay-solutions I've added my use case to the description so you may understand my intentions better now. What is your use case?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dallyger in that case getNonNullOrFail would be a better naming.
We load all envs in a config dynamically and then we cache the config. We also use envs that are defined as null => configs that are defined as null.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not totally convinced yet, because your example seems to deviate from the default out of the box experience. And if you know you do that you should know not to use that helper. I mean this does not change any existing behavior, only adds some small new feature.

Let's wait for some more feedback from other peoples and check what they think about it.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getOrFail is analog to findOrFail firstOrFail in db. If the row is there is should not throw. Analog if the config exists, no matter the value it should not throw.

Copy link
Contributor Author

@dallyger dallyger Jul 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. Further testing showed me, that Env::getOrFail('FOO'), which this change was based upon, does return null, if FOO=null is set in .env. This was indeed an oversight on my end and due to a lot of indirection in the code not trivial to understand. Do you have suggestions for the error message and the helper function too? config_non_null_or_fail() seems like a rather long name for a small helper.

Copy link
Contributor

@rojtjo rojtjo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could use this trick to allow null values.

It's done in multiple places throughout the framework such as here:

/**
* Determine if an item exists in the collection.
*
* @param (callable(TValue, TKey): bool)|TValue|string $key
* @param mixed $operator
* @param mixed $value
* @return bool
*/
public function contains($key, $operator = null, $value = null)
{
if (func_num_args() === 1) {
if ($this->useAsCallable($key)) {
$placeholder = new stdClass;
return $this->first($key, $placeholder) !== $placeholder;
}
return in_array($key, $this->items);
}
return $this->contains($this->operatorForWhere(...func_get_args()));
}

*/
public function getOrFail(string $key)
{
return $this->get($key) ?? throw new RuntimeException("Configuration value for key [$key] is not set.");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return $this->get($key) ?? throw new RuntimeException("Configuration value for key [$key] is not set.");
$placeholder = new stdClass();
$value = $this->get($key, $placeholder);
if ($value === $placeholder) {
throw new RuntimeException("Configuration value for key [$key] is not set.");
}
return $value;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you very much for this trick! However, I'm currently undecided on how I would like to proceed with this PR, as that would fix the issue mentioned above, but also deviate from my use case and no longer solve my issue. Will take some time to think about it.

@@ -8,6 +8,7 @@
use Illuminate\Support\Collection;
use Illuminate\Support\Traits\Macroable;
use InvalidArgumentException;
use RuntimeException;

Copy link
Contributor

@rojtjo rojtjo Jul 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
use stdClass;

@dallyger dallyger marked this pull request as draft July 10, 2025 13:08
@shaedrich
Copy link
Contributor

config_or_fail

I don't know but this doesn't read well. It looks … clunky 🤔 Maybe, we should consider adapting conventions like Ruby (Laravel is based on Rails anyway) has (e.g. functionName? means, a boolean is returned, while object.methodName! means, the object is modified instead of immutably returned), although, I have no character at the top of my head to suggest right now

@dallyger
Copy link
Contributor Author

For me personally, any style seems fine. Would be nice if they're consistent. My thought was, that there is a config_path() helper calling app()->configPath(). So I replicated the camel-case to snake-case conversion. But any other suggestion is welcome aswell.

@macropay-solutions
Copy link

@dallyger a solution for you only via macro would be the best. No headaches.

@dallyger
Copy link
Contributor Author

@macropay-solutions Thank you! How could I not think about macros? I was literally using one on the same line! Also iterated on your suggestion for a name better matching the action it does.

My solution now is this:

use Illuminate\Support\Facades\Config;

# AppServiceProvider.php

Config::macro('getNonNull', fn (string $key) => Config::get($key)
    ?? throw new RuntimeException("Configuration value for key [$key] is empty."));
    
# usage:
Config::getNonNull('foo.bar');

@donnysim
Copy link
Contributor

I get the point but I'm not following the reasoning. If you're deploying or expecting some config values to be set, checking them on runtime at the point of usage seems kind of dubious at best. You would check them all before doing anything (during bootstrapping etc.) to ensure nothing ends up partially broken or incorrect, not fail as it happens - especially on deployments. Most of those cases can be just written by

throw_unless(config('deploy.key'), new RuntimeException('Deploy key is not set.'));

or such where you can provide a more detailed error message if needed etc.. If you're conditionally throwing like in your example:

if (config('services.xyz.active')) {
  Http::post(config_or_fail('services.xyz.domain').'/up');
}

you'll most likely want to throw a custom exception that can be handled by the global exception handler or try/catch for that specific case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants